#include <BulletDynamics\Dynamics\btRigidBody.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <btBulletCollisionCommon.h>

#include "Level.hpp"
#include "dotscene\DotScene.h"
#include "BtOgre\BtOgreGP.h"
#include "EventManager.hpp"
#include "Action.hpp"

/*
	Generic object rotator action, assign a rigid body to an instance of this class and call update(dt) to
	perform rotation.
	Pivot specifies what point is the origin of the rotation.
	Speed is the rotation speed in radians.
*/
class ObjectRotateAction : public Action
{
	public:
		ObjectRotateAction(std::list<Action*>* al, btRigidBody* rb, const btVector3& axis, const btVector3& pivot, double speed, double rotatelimit)
			: m_Running(false), m_ActionList(al), m_RB(rb), m_Axis(axis), m_Pivot(pivot), m_Speed(speed), m_RotateLimit(rotatelimit)
		{ m_StartAngle = m_Angle = 0; }

		virtual const char* getName() { return "ObjectRotate"; }

		//when we first receive this event we register it as a repeatable action
		virtual void process()
		{
			if (!m_Running)
			{
				m_ActionList->push_back(this);
				m_Running = true;
			}
		}

		virtual void update(double dt)
		{
			m_Angle += m_Speed * (dt/1000);
			
			char msg[200];

			btTransform &startTransform = m_RB->getWorldTransform();

			btQuaternion quat = startTransform.getRotation();
			quat.setRotation(m_Axis, m_Angle);
			startTransform.setRotation(quat);
			startTransform.setOrigin(m_Pivot);
			m_RB->getMotionState()->setWorldTransform(startTransform);
			

			//remove the action from the list of repeatables when we have moved far enough
			if ((m_Speed < 0 && m_Angle < m_RotateLimit) || (m_Speed > 0 && m_Angle > m_RotateLimit))
			//if (m_Angle < -3.14)
			{
				Ogre::LogManager::getSingleton().logMessage("removing from action list");
				m_ActionList->remove(this);
				//uncomment the next line to make the rotator possible to use more than once
				//m_Running = false;
				//m_StartAngle = m_Angle = m_RB->getOrientation().getAngle();
			}
		}

	private:
		bool					m_Running;
		double					m_Angle, m_StartAngle;
		std::list<Action*>*		m_ActionList;
		btRigidBody*			m_RB;

		btVector3				m_Axis;
		btVector3				m_Pivot;
		double					m_Speed;
		double					m_RotateLimit;
		
};


void Level::nextLevel()
{
	if ((m_currentLevel == -1) && (m_Level.size() > 0))
	{
		m_currentLevel = 0;
	} else
	{
		if (m_Level[m_currentLevel].nextlevel != "")
			m_currentLevel = m_LevelIndex[m_Level[m_currentLevel].nextlevel];
	}
	
	changeLevel(m_Level[m_currentLevel].title);
}

void Level::resetCurrentLevel()
{
	changeLevel(m_Level[m_currentLevel].title);
}

void Level::add(const LevelInfo& li)
{

	m_Level.push_back(li);
	m_LevelIndex[li.title] = m_Level.size() - 1;
}

void Level::changeLevel(std::string name)
{
	char msg[255];
	

	if (m_currentLevel != -1)
	{
		//this means we have another level currently loaded, so we need to cleanup first
		sprintf(msg, "Performing cleanup of old level");
		Ogre::LogManager::getSingleton().logMessage(msg);
		cleanup(m_pLevelAttachNode, 0);
	}


	int index = m_LevelIndex[name];
	std::string filename = m_Level[index].scenefile;
	m_currentLevel = index;
	sprintf(msg, "loading scenefile: %s", filename.c_str());
	Ogre::LogManager::getSingleton().logMessage(msg);
	DotSceneLoader dsl;
	dsl.parseDotScene(filename, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, m_pSceneManager, m_pLevelAttachNode);

	sprintf(msg, "Switching to level index: %d, title: %s", m_currentLevel, m_Level[m_currentLevel].title.c_str());
	Ogre::LogManager::getSingleton().logMessage(msg);

	/*
		create collision data for the loaded scene
	*/

	sprintf(msg, "child count: %d", m_pLevelAttachNode->numChildren());
	Ogre::LogManager::getSingleton().logMessage(msg);

	Ogre::Node::ChildNodeIterator iter = m_pLevelAttachNode->getChildIterator();
	
	while ( iter.hasMoreElements() )
	{
		Ogre::SceneNode* child = static_cast<Ogre::SceneNode*>(iter.current()->second);
		bool validEntity = false;
		Ogre::Entity* ent = 0;

		try
		{
			ent = (Ogre::Entity*)(child->getAttachedObject(0));
			
			validEntity = true;
		} catch (...)
		{
			validEntity = false;
		}
		if (!validEntity)
		{
			iter.getNext();
			continue;
		}

		Ogre::MeshPtr mesh = ent->getMesh();

		//construct a trimesh collision shape from the mesh
		//we also get it's current transformation (orientation and scale), so the collision shape get's the correct transformation
		BtOgre::StaticMeshToShapeConverter smsc;
		Ogre::Matrix4 transform = Ogre::Matrix4::IDENTITY;
		Ogre::Matrix3 trans3;
		child->getOrientation().ToRotationMatrix(trans3);
		transform.setScale(child->getScale());
		transform = transform.concatenate(trans3);
		
		smsc.addMesh(mesh, transform);
		
		btCollisionShape* colShape = smsc.createTrimesh();
		m_CollisionShapes.push_back(colShape);

		btDefaultMotionState* tempMS = new btDefaultMotionState(btTransform( btQuaternion(0,0,0,1), btVector3(child->getPosition().x , child->getPosition().y, child->getPosition().z) ));
		btRigidBody::btRigidBodyConstructionInfo rbi(0, tempMS, colShape, btVector3(0,0,0));
		btRigidBody* rb = new btRigidBody(rbi);
		m_pDynamicsWorld->addRigidBody(rb, 1, 12);
		//for levels, we also keep track of all rigid bodies so they can easily be removed when we switch between levels
		m_RigidBodies.push_back(rb);

		/*
			now that we have created the physics/collision data for this object, let's check if this object
			has any special data associated with it
		*/
		handleProperties(&dsl, child, rb);
		
		iter.getNext();
	}
}

void Level::handleProperties(DotSceneLoader* dsl, Ogre::SceneNode* node, btRigidBody* rb)
{
	char msg[200];

	bool createTV = false;
	bool setKinematic = false;

	int tvRadius = 0;
	Ogre::String postEvent = "", listenEvent = "", eventResponse = "", triggerShape = "";
	btVector3 rotateAxis, rotatePivot, boxSize;
	double rotateSpeed = 0, rotateLimit = 0;

	for (int i = 0; i < dsl->nodeProperties.size(); i++)
	{
		//see if our node exists in the property list
		if (dsl->nodeProperties[i].nodeName == node->getName())
		{
			Ogre::String& propName = dsl->nodeProperties[i].propertyNm;
			Ogre::String& value = dsl->nodeProperties[i].valueName;
			sprintf(msg, "nodeName: %s propName: %s valueName: %s", dsl->nodeProperties[i].nodeName.c_str(), propName.c_str(), dsl->nodeProperties[i].valueName.c_str() );
			Ogre::LogManager::getSingleton().logMessage(msg);

			if (propName == "trigger" && value == "yes")
				createTV = true;
			if (propName == "triggerRadius")
				tvRadius = Ogre::StringConverter::parseInt(value);
			if (propName == "triggerBoxSize")
				boxSize = BtOgre::Convert::toBullet(Ogre::StringConverter::parseVector3(value));
			if (propName == "triggerShape")
				triggerShape = value;
			if (propName == "postEvent")
				postEvent = value;

			if (propName == "kinematic" && value == "yes")
				setKinematic = true;
			if (propName == "listenEvent")
				listenEvent = value;
			if (propName == "eventResponse")
				eventResponse = value;
			if (propName == "rotatePivot")
				rotatePivot = BtOgre::Convert::toBullet(Ogre::StringConverter::parseVector3(value));
			if (propName == "rotateAxis")
				rotateAxis = BtOgre::Convert::toBullet(Ogre::StringConverter::parseVector3(value));
			if (propName == "rotateSpeed")
				rotateSpeed = Ogre::StringConverter::parseReal(value);
			if (propName == "rotateLimit")
				rotateLimit = Ogre::StringConverter::parseReal(value);
		}
	}

	if (createTV)
	{
		if (triggerShape == "")
		{
			sprintf(msg, "Warning: No trigger shape specified. Using sphere shape as default.", tvRadius);
			Ogre::LogManager::getSingleton().logMessage(msg);
			triggerShape = "sphere";
		}
		if (triggerShape == "sphere" && tvRadius < 1)
		{
			sprintf(msg, "Error: Unable to create sphere trigger volume. No radius or invalid radius specified.", tvRadius);
			Ogre::LogManager::getSingleton().logMessage(msg);
			createTV = false;
		}
		if (triggerShape == "box" && boxSize == btVector3(0,0,0))
		{
			sprintf(msg, "Error: Unable to create box trigger volume. No size or invalid size specified.", tvRadius);
			Ogre::LogManager::getSingleton().logMessage(msg);
			createTV = false;
		}
		if (postEvent == "")
		{
			sprintf(msg, "Error: No event to post is specified. Will not create trigger volume.");
			Ogre::LogManager::getSingleton().logMessage(msg);
			createTV = false;
		}

		if (createTV)
		{
			sprintf(msg, "Creating trigger volume for node: %s postevent: %s", node->getName().c_str(), postEvent.c_str() );
			Ogre::LogManager::getSingleton().logMessage(msg);
		
		
			btGhostObject* go = new btPairCachingGhostObject;
			btCollisionShape* colShape = 0;
			if (triggerShape == "sphere")
				colShape = new btSphereShape(tvRadius);
			if (triggerShape == "box")
				colShape = new btBoxShape(boxSize);
			m_CollisionShapes.push_back(colShape);
			go->setCollisionShape(colShape);
			btTransform transform;
			transform.setIdentity();
			transform.setOrigin(BtOgre::Convert::toBullet(node->_getDerivedPosition()));
			go->setWorldTransform(transform);
			go->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);
			m_pDynamicsWorld->addCollisionObject(go, 2, 4);
			
			VolumeTriggerInfo vti;
			vti.go = go;
			vti.postEvent = postEvent;
			m_VolumeTrigger.push_back(vti);
		}
	}

	if (setKinematic)
	{
		sprintf(msg, "Setting %s to be kinematic.", node->getName().c_str());
		Ogre::LogManager::getSingleton().logMessage(msg);

		rb->setCollisionFlags(rb->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
		rb->setActivationState(DISABLE_DEACTIVATION);

		if (listenEvent == "")
		{
			Ogre::LogManager::getSingleton().logMessage("Unable to register listener. No event specified.");
			setKinematic = false;
		}
		if (eventResponse == "")
		{
			Ogre::LogManager::getSingleton().logMessage("No event response specified. Will not proceed to register the listener.");
			setKinematic = false;
		}

		if (setKinematic)
			if (eventResponse == "objectRotate")
			{
				m_EventList.push_back(listenEvent);
				sprintf(msg, "speed: %f limit: %f", rotateSpeed, rotateLimit );
				Ogre::LogManager::getSingleton().logMessage(msg);
				m_pEM->registerListener(listenEvent, new ObjectRotateAction(&m_Actions, rb, rotateAxis, rotatePivot, rotateSpeed, rotateLimit));
				KinematicObjectInfo koi;
				koi.rb = rb;
				
				koi.node = node;
				m_KinematicObjects.push_back(koi);
			}
	}
}

void Level::cleanup(Ogre::Node* node, int depth)
{
	char msg[255];

	/*
		Iterate recursively over all scene nodes created for the current level and ensure that we detach
		and destroy every entity created.
	*/

	//Ogre::SceneNode* child = static_cast<Ogre::SceneNode*>(iter.current()->second);
	Ogre::SceneNode* child = static_cast<Ogre::SceneNode*>(node);

	sprintf(msg, "Node: %s has %d attached objects and %d children", child->getName().c_str(), child->numAttachedObjects(), child->numChildren());
	Ogre::LogManager::getSingleton().logMessage(msg);

	Ogre::SceneNode::ObjectIterator coiter = child->getAttachedObjectIterator();
	while (coiter.hasMoreElements())
	{
		Ogre::Entity* ent = (Ogre::Entity*)(child->getAttachedObject(0));
			
		sprintf(msg, "Deleting entity with name: %s node children: %d", ent->getName().c_str(), child->numChildren());
		Ogre::LogManager::getSingleton().logMessage(msg);

		child->detachObject(static_cast<unsigned short>(0));
		m_pSceneManager->destroyEntity(ent);
		
		coiter = child->getAttachedObjectIterator();
	}
		
	if (child->numChildren() > 0)
	{
		for (int i = 0; i < child->numChildren(); i++)
		{
			cleanup(child->getChild(i), depth + 1);
		}
	}
	
	//we only want to do this once
	if (depth == 0)
	{
		m_pLevelAttachNode->removeAndDestroyAllChildren();
		
		//delete and remove all rigid bodies
		for (int i = 0; i < m_RigidBodies.size(); i++)
		{
			m_pDynamicsWorld->removeRigidBody(m_RigidBodies[i]);
			delete m_RigidBodies[i]->getMotionState();
			delete m_RigidBodies[i];
		}
		m_RigidBodies.clear();

		//delete and remove all collision shapes created
		for (int i = 0; i < m_CollisionShapes.size(); i++)
		{
			delete m_CollisionShapes[i];
		}
		m_CollisionShapes.clear();

		//delete and remove all volume triggers
		for (int i = 0; i < m_VolumeTrigger.size(); i++)
		{
			m_pDynamicsWorld->removeCollisionObject(m_VolumeTrigger[i].go);
			delete m_VolumeTrigger[i].go;
		}
		m_VolumeTrigger.clear();

		//we do not delete actions from this vector because that is the responsibility of the event manager
		//Note: this should be handled better by storing shared_ptr or weak_ptr in the vector
		m_Actions.clear();

		for (int i = 0; i < m_EventList.size(); i++)
			m_pEM->clearEventListeners( m_EventList[i] );
		m_EventList.clear();

		//clear the list of kinematic objects
		m_KinematicObjects.clear();
	}
}

void Level::update(double dt)
{
	//check our volume triggers
	for (int i = 0; i < m_VolumeTrigger.size(); i++)
	{
		if (m_VolumeTrigger[i].go->getNumOverlappingObjects() > 0)
		{
			//we got something inside our volume trigger, let's post the event associated with it
			//the decision whether to do anything at all after this is handled by the listener
			m_pEM->postEvent(m_VolumeTrigger[i].postEvent);
		}
	}

	//update any actions going on in the world
	std::list<Action*>::iterator iter = m_Actions.begin();
	std::vector<Action*> vecPtr;

	while (iter != m_Actions.end())
	{
		vecPtr.push_back(*iter);
		iter++;
	}

	for (int i = 0; i < vecPtr.size(); i++)
		vecPtr[i]->update(dt);

	for (int i = 0; i < m_KinematicObjects.size(); i++)
	{
		m_KinematicObjects[i].node->setOrientation( BtOgre::Convert::toOgre(m_KinematicObjects[i].rb->getWorldTransform().getRotation()) );
		m_KinematicObjects[i].node->setPosition( BtOgre::Convert::toOgre(m_KinematicObjects[i].rb->getWorldTransform().getOrigin()) );
	}
}